#include <iostream>
#include <thread>

#ifdef __cplusplus
extern "C"
{
#endif
#include <stdio.h>
#include <dlfcn.h>
#include <pthread.h>
#include <unistd.h>

#ifdef __cplusplus
}
#endif


pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex5 = PTHREAD_MUTEX_INITIALIZER;

typedef int (*poxic_pthread_mutex_lock)(pthread_mutex_t *__mutex);
typedef int (*poxic_pthread_mutex_unlock)(pthread_mutex_t *__mutex);

poxic_pthread_mutex_lock   p_pthread_mutex_lock   = nullptr;
poxic_pthread_mutex_unlock p_pthread_mutex_unlock = nullptr;

void* task1(void* a)
{
    printf("task1:%ld\n", pthread_self());
    pthread_mutex_lock(&mutex1);
    sleep(1);
    pthread_mutex_lock(&mutex2);
    printf("task1\n");
    pthread_mutex_unlock(&mutex2);

    pthread_mutex_unlock(&mutex1);
}

void* task2(void* a)
{
    printf("task2:%ld\n", pthread_self());
    pthread_mutex_lock(&mutex2);
    sleep(1);
    pthread_mutex_lock(&mutex3);
    printf("task2\n");
    pthread_mutex_unlock(&mutex3);

    pthread_mutex_unlock(&mutex2);
}

void* task3(void* a)
{
    printf("task3:%ld\n", pthread_self());
    pthread_mutex_lock(&mutex3);
    sleep(1);
    pthread_mutex_lock(&mutex4);
    printf("task3\n");
    pthread_mutex_unlock(&mutex4);

    pthread_mutex_unlock(&mutex3);
}

void* task4(void* a)
{
    printf("task4:%ld\n", pthread_self());
    pthread_mutex_lock(&mutex4);
    sleep(1);
    pthread_mutex_lock(&mutex5);
    printf("task4\n");
    pthread_mutex_unlock(&mutex5);


    pthread_mutex_unlock(&mutex4);
}

void* task5(void* a)
{
    printf("task5:%ld\n", pthread_self());
    pthread_mutex_lock(&mutex5);
    sleep(1);
    pthread_mutex_lock(&mutex1);
    printf("task5\n");
    pthread_mutex_unlock(&mutex1);

    pthread_mutex_unlock(&mutex5);
}


#include <list>
#include <mutex>
#include <chrono>
#include "graph.h"

typedef struct MutexInfo
{
    uint64_t    mutex_add;
    pthread_t   thread_id;
} MutexInfo;


static std::list<MutexInfo> g_minfo;
static Graph<MutexInfo> g_graph;
// static std::mutex g_check_mutex;


void before_lock(pthread_t tid, pthread_mutex_t *__mutex)
{
    for (const auto item : g_minfo)
    {
        if ((uint64_t)__mutex == item.mutex_add)
        {
            // list 里面有则+边
            Graph<MutexInfo>::vertex from_v;
            from_v.id = item.thread_id;
            from_v.data.thread_id = item.thread_id;
            from_v.data.mutex_add = (uint64_t)__mutex;

            Graph<MutexInfo>::vertex to_v;
            to_v.id = tid;
            to_v.data.thread_id = tid;
            to_v.data.mutex_add = (uint64_t)__mutex;

            // {
                // std::lock_guard<std::mutex> lk{g_check_mutex};
                g_graph.add_vertex(from_v);
                g_graph.add_vertex(to_v);

                g_graph.add_edge(from_v, to_v);
            // }
            return;
        }
    }
}

void after_lock(pthread_t tid, pthread_mutex_t *__mutex)
{
    bool exit = false;
    for (const auto item : g_minfo)
    {
        if ((uint64_t)__mutex == item.mutex_add)
        {
            exit = true;
            Graph<MutexInfo>::vertex from_v;
            from_v.id = item.thread_id;
            from_v.data.thread_id = item.thread_id;
            from_v.data.mutex_add = (uint64_t)__mutex;

            Graph<MutexInfo>::vertex to_v;
            to_v.id = tid;
            to_v.data.thread_id = tid;
            to_v.data.mutex_add = (uint64_t)__mutex;

            // std::lock_guard<std::mutex> lk{g_check_mutex};
            g_graph.remove_edge(from_v, to_v);
            break;
        }
    }
    if (!exit)
    {
        MutexInfo m_info;
        m_info.thread_id = tid;
        m_info.mutex_add = (uint64_t)__mutex;
        // std::lock_guard<std::mutex> lk{g_check_mutex};
        g_minfo.push_back(m_info);
    }
}

void after_unlock(pthread_t tid, pthread_mutex_t *__mutex)
{
    for (auto item = g_minfo.begin(); item != g_minfo.end(); item++)
    {
        if ((uint64_t)__mutex == item->mutex_add)
        {
            // std::lock_guard<std::mutex> lk{g_check_mutex};
            g_minfo.erase(item);
            break;
        }
    }
}

void* check_circle_task(void* a)
{
    (void)a;
    static int64_t his_time{};

    while (true)
    {
        int64_t now_millis = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();

        if (now_millis > his_time + 3 * 1000)
        {
            // std::lock_guard<std::mutex> lk{g_check_mutex};
            g_graph.print_thread_mutex();
            g_graph.traverse();
            his_time = now_millis;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}

int pthread_mutex_lock (pthread_mutex_t *__mutex)
{
    printf("pthread_mutex_lock\n");
    // std::cout << "" << std::endl;
    pthread_t tid = pthread_self();
    before_lock(tid ,__mutex);
    p_pthread_mutex_lock(__mutex);
    after_lock(tid, __mutex);
    return 0;
}

int pthread_mutex_unlock (pthread_mutex_t *__mutex)
{
    printf("pthread_mutex_unlock\n");
    p_pthread_mutex_unlock(__mutex);
    after_unlock(pthread_self(), __mutex);
    return 0;
}



int main()
{

    pthread_t check_thread;
    p_pthread_mutex_lock   = (poxic_pthread_mutex_lock)dlsym(RTLD_NEXT, "pthread_mutex_lock");
    p_pthread_mutex_unlock = (poxic_pthread_mutex_unlock)dlsym(RTLD_NEXT, "pthread_mutex_unlock");
    if (!p_pthread_mutex_lock || !p_pthread_mutex_unlock)
    {
        printf("dlsym failed!\n");
        return -1;
    }
    pthread_create(&check_thread, NULL, check_circle_task, NULL);

    pthread_t tid1, tid2, tid3, tid4, tid5;
    pthread_create(&tid1, NULL, task1, NULL);
    pthread_create(&tid2, NULL, task2, NULL);
    pthread_create(&tid3, NULL, task3, NULL);
    pthread_create(&tid4, NULL, task4, NULL);
    pthread_create(&tid5, NULL, task5, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    pthread_join(tid5, NULL);
    pthread_join(check_thread, NULL);
    return 0;
}